package handler;

import configuration.MyConfiguration;
import exception.ReadTimeOutSleepTime;
import http.HttpMethod;
import http.MyHttpRequest;
import http.MyHttpResponse;
import pool.TaskFactory;
import pool.ThreadManager;
import util.HttpUtil;
import util.MyUtil;
import util.reg.RegEx;
import util.reg.RegFactory;

import java.io.*;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * 在writeTask中，我们将缓存所有GET，有效时间10分钟
 * 文件命名规则：url timestamp
 * @author Karl
 * @Date 2023/1/2 - 17:30
 */

public class CacheHandler implements Handler  {

    private static final ThreadLocal<MyHttpResponse> RESPONSE_THREAD_LOCAL = new ThreadLocal<>();

    private static String lineSeparator = System.lineSeparator();
    private static Charset utf_8 = StandardCharsets.UTF_8;
    private static byte[] lineByte2 = (lineSeparator + lineSeparator).getBytes(utf_8);


    @Override
    public boolean handler(MyHttpRequest request, Socket from, Socket to) {
        MyHttpResponse response = RESPONSE_THREAD_LOCAL.get();
        RESPONSE_THREAD_LOCAL.remove();
        try {
            response.sendMessage(from.getOutputStream());
        } catch (IOException e) {
            MyUtil.log("exception : %s %s", e.getClass(), e.getLocalizedMessage());
            return true;
        }
        return false;
    }

    @Override
    public boolean isSupport(MyHttpRequest request,Socket remote) {
        if(request.getMethod()!= HttpMethod.GET) return false;
        //判断是否有缓存
        File fileName = null;
        fileName = new File(MyConfiguration.getFileRoot() + request.getUrl());
        if (!fileName.exists()) return false;
        InputStream f = null;
        try {
            MyHttpResponse response = new MyHttpResponse();
            f = new FileInputStream(fileName);
//            f = new RandomAccessFile(fileName, "r");
            if (!readResponseLine(response,f)) {
                return false;
            }
            byte[] buf = new byte[8196];
            if (!readResponseHeader(response,f,buf)) {
                return false;
            }
            //本来就是要去询问的，我就不多次一句了，直接给转发处理器ForwardHandle去处理就好了
            if(request.getRequestHeader("If-Modified-Since")!=null||
                    request.getRequestHeader("If-None-Match")!=null) return false;
            //到这里请求行和请求头都拿出来了,判断我们在缓存时放入的请求头的过去时间
            long time;
            if ((time = response.getCacheExpire())>0) {
                //未到期，把剩余的文件内容取出，并换掉响应头
                if (!readResponseBody(response,f)) {
                    return false;
                }
                response.setResponseLine("HTTP/1.1 200 Cache From MyProxy\r\n");
                RESPONSE_THREAD_LOCAL.set(response);
                return true;
            }else{
                String header = null;
                MyHttpRequest ask = null;
                //缓存的响应报文中有这两个响应头
                if((header = response.getResponseHeader("ETag"))!=null){
                    ask = new MyHttpRequest();
                    ask.setRequestLine(request.getRequestLine());
                    if (!request.cloneHeader(ask)) {
                        return false;
                    }
                    ask.addRequestHeader("If-None-Match: "+header.split(": ")[1]);
                }else if((header = response.getResponseHeader("Last-Modified"))!=null){
                    ask = new MyHttpRequest();
                    ask.setRequestLine(request.getRequestLine());
                    if (!request.cloneHeader(ask)) {
                        return false;
                    }
                    ask.addRequestHeader("If-Modified-Since: "+header.split(": ")[1]);
                }
                if(ask!=null){
                    //发送ask并等待响应
                    ask.sendOrigin(remote.getOutputStream(),null);
                    //接收新的响应
                    MyHttpResponse askResponse = new MyHttpResponse();
                    InputStream is = remote.getInputStream();
                    if (!readResponseLine(askResponse, is)) {
                        return false;
                    }
                    if (!readResponseHeader(askResponse,is,buf)) {
                        return false;
                    }
                    receiveBody(askResponse,is);

                    int status = askResponse.getStatus();
                    if(status==304) {
                        //未到期，把剩余的文件内容取出，并换掉响应头
                        if (!readResponseBody(response,f)) {
                            return false;
                        }
                        response.setResponseLine("HTTP/1.1 200 Cache From MyProxy\r\n");
                        //没有发生变化，可以直接返回
                        RESPONSE_THREAD_LOCAL.set(response);
                        return true;
                    }else if(status>=200&&status<300){
                        //返回了其他成功请求的响应，将askResponse返回即可
                        RESPONSE_THREAD_LOCAL.set(askResponse);
                        //启动异步线程去延迟处理这个问题，将老的文件换成新的文件
                        ThreadManager.getManager().execute(TaskFactory.replaceFile(request,askResponse));
                        return true;
                    }

                }
            }


        } catch (IOException e) {
            MyUtil.log("exception : %s %s", e.getClass(), e.getLocalizedMessage());
        }finally {
            MyUtil.closeIo(f);
        }
        return false;
    }

    /**
     * 文件读取请求体，因为只需要把剩下的都都进去比较简单，比较receiveBody方法
     * @param response 响应对象
     * @param is 文件字节流
     * @return 读取是否成功
     */

    private boolean readResponseBody(MyHttpResponse response, InputStream is) {
        int now = -1;
        try {
            while((now = is.read())!=-1){
                response.addResponseBody((byte) now);
            }
        } catch (IOException e) {
            return false;
        }
        return true;
    }


    private boolean readResponseHeader(MyHttpResponse response, InputStream is, byte[] buf) {
        int now = -1,status = -1, off = 0;
        try {
            while ((now = is.read())!=-1){
                buf[off++] = (byte) now;
                if(lineByte2[status+1]==now){
                    status++;
                    if(status == 1){
                        response.addResponseHeader(new String(buf,0,off));
                        off = 0;
                    }else if (status == 3){
                        return true;
                    }
                }else{
                    status = -1;
                }
            }
        } catch (IOException e) {
            return false;
        }
        return false;
    }

    private boolean readResponseLine(MyHttpResponse response, InputStream is) throws IOException {
        int now = -1;
        RegEx lineReg = RegFactory.createResponseLineReg();
        String line = null;
        while((now = is.read())!=-1){
            if((line = lineReg.enterChar((byte) now))!=null){
                response.setResponseLine(line);
                return true;

            }
        }
        return false;

    }

    /**
     * 接收询问是否改变的请求对应的响应的响应体
     *
     * @param response 响应对象，存储数据
     * @param is 服务器的连接的socket的inputStream
     */
    private void receiveBody(MyHttpResponse response, InputStream is) throws IOException {
        int now = -1;
        int responseStatus = response.getStatus();
        if (responseStatus >= 100) {
            //1xx不管响应体。直接返回
            if (responseStatus < 200)
                return;
            else if (responseStatus < 300) {
                if (responseStatus == 204) return;
                //其他都可能需要判断是否需要缓存
                //判断是否需要缓存
                if (HttpUtil.needCache(response)) {
                    //缓存
                    response.setNeedCache(true);
                }
            }else{
                if(responseStatus==304){
                    if (HttpUtil.needCache(response)) {
                        //缓存
                        response.setNeedCache(true);
                    }
                    return;
                }
            }
        }
        int len = -1;
        //开始接收响应体
        if ((len = response.getContentLength()) != -1) {
            while (len > 0) {
                while (len > 0 && (now = is.read()) != -1) {
                    response.addResponseBody((byte) now);
                    len--;
                }
            }
        } else if (response.isChunked()) {
            //通过chunked读取响应体
            len = getChunkedLen(is, response);
            while (len > 0) {
                while (len > 0) {
                    if ((now = is.read()) != -1) {
                        response.addResponseBody((byte) now);
                        len--;
                    }
                }
                //写死，读完之后一定是CRLF
                readCRLF(is,response);
                len = getChunkedLen(is, response);

            }
            readCRLF(is,response);

        }
    }


    /**
     * 读取chunked的首行，即一个十六进制的数表示接下来消息块的长度
     * @param is 目标服务器的inputStream
     * @param response 这次响应的HTTP对象
     * @return  16机制转成十进制的字节数
     * @throws IOException
     */

    private int getChunkedLen(InputStream is, MyHttpResponse response) throws IOException {
        int status = -1, off = 0, now = -1, len = -1,sleepTime = 50;
        byte[] buf = new byte[32];
        while (true) {
            if ((now = is.read()) != -1){
                sleepTime = 50;
                buf[off++] = (byte) now;
                response.addResponseBody((byte) now);
                if (now == lineByte2[status + 1]) {
                    status++;
                    if (status == 1) {
                        len = Integer.parseInt(new String(buf, 0, off).trim(), 16);
                        return len;
                    }
                } else {
                    status = -1;
                    if (now == lineByte2[status + 1]) {
                        status++;
                    }
                }
            }else{
                try {
                    Thread.sleep(sleepTime);
                    sleepTime+=10;
                    if(sleepTime>600) throw new ReadTimeOutSleepTime("在getChunkedLen读取超时");
                } catch (InterruptedException e) {
                    return 0;
                }

            }
        }
    }


    /**
     * 读取掉is接下来的两个字节，而且必须是\r\n
     * @param is 目标服务器的inputStream
     * @param response 这次响应的HTTP对象
     * @throws IOException 如果读到的不是\r\n就抛异常，结束此次连接
     */
    private void readCRLF(InputStream is, MyHttpResponse response) throws IOException {
        int now = -1 ;
        //按道理应该给个sleepTime 超时抛异常的，但是算了，让timeout直接让socket抛依次就好了
        while(true){
            if((now = is.read()) > 0){
                response.addResponseBody((byte) now);
                if(now!=lineByte2[0]) throw new IOException("chunked后不是换行\\r");
                break;
            }
        }
        while(true){
            if((now = is.read()) > 0){
                response.addResponseBody((byte) now);
                if(now!=lineByte2[1]) throw new IOException("chunked后不是换行\\n");
                break;
            }
        }
    }





}
